package GMDPRE

func T(value int) uint32 {
	if value < 16 {
		return 0x79cc4519
	}
	return 0x7a879d8a
}

func FF(X uint32, Y uint32, Z uint32, value int) uint32 {
	if value < 16 {
		return X ^ Y ^ Z
	}
	return (X & Y) | (X & Z) | (Y & Z)
}

func GG(X uint32, Y uint32, Z uint32, value int) uint32 {
	if value < 16 {
		return X ^ Y ^ Z
	}
	return (X & Y) | ((^X) & Z)
}

func ROTL(X uint32, n int) uint32 {
	n = n & 0x1f
	return (X << n) | (X >> (32 - n))
}

func P0(X uint32) uint32 {
	return X ^ ROTL(X, 9) ^ ROTL(X, 17)
}

func P1(X uint32) uint32 {
	return X ^ ROTL(X, 15) ^ ROTL(X, 23)
}

// 消息扩展
func msgExtend(ctx *Sm3Ctx, w []uint32, nw []uint32) {
	var i int
	for i = 0; i < 16; i++ {
		w[i] = (uint32(ctx.buffer[i*4]) << 24) | (uint32(ctx.buffer[i*4+1]) << 16) | (uint32(ctx.buffer[i*4+2]) << 8) | uint32(ctx.buffer[i*4+3])
	}
	for i = 16; i < 68; i++ {
		w[i] = P1(w[i-16]^w[i-9]^ROTL(w[i-3], 15)) ^ ROTL(w[i-13], 7) ^ w[i-6]
	}
	for i = 0; i < 64; i++ {
		nw[i] = w[i] ^ w[i+4]
	}
}

func CF(ctx *Sm3Ctx, w []uint32, nw []uint32) {
	var j int
	var A, B, C, D, E, F, G, H uint32
	var SS1, SS2, TT1, TT2 uint32

	A = ctx.state[0]
	B = ctx.state[1]
	C = ctx.state[2]
	D = ctx.state[3]
	E = ctx.state[4]
	F = ctx.state[5]
	G = ctx.state[6]
	H = ctx.state[7]

	for j = 0; j < 64; j++ {
		SS1 = ROTL(ROTL(A, 12)+E+ROTL(T(j), j), 7)
		SS2 = SS1 ^ ROTL(A, 12)
		TT1 = FF(A, B, C, j) + D + SS2 + nw[j]
		TT2 = GG(E, F, G, j) + H + SS1 + w[j]
		D = C
		C = ROTL(B, 9)
		B = A
		A = TT1
		H = G
		G = ROTL(F, 19)
		F = E
		E = P0(TT2)
	}

	ctx.state[0] ^= A
	ctx.state[1] ^= B
	ctx.state[2] ^= C
	ctx.state[3] ^= D
	ctx.state[4] ^= E
	ctx.state[5] ^= F
	ctx.state[6] ^= G
	ctx.state[7] ^= H
}

func Sm3Init(ctx *Sm3Ctx) {
	ctx.pos = 0
	ctx.msgLen = 0

	/* 初始向量IV */
	ctx.state[0] = 0x7380166F
	ctx.state[1] = 0x4914B2B9
	ctx.state[2] = 0x172442D7
	ctx.state[3] = 0xDA8A0600
	ctx.state[4] = 0xA96F30BC
	ctx.state[5] = 0x163138AA
	ctx.state[6] = 0xE38DEE4D
	ctx.state[7] = 0xB0FB0E4E
}

func Sm3Update(ctx *Sm3Ctx, input []uint8, inLen uint64) {
	if inLen == 0 {
		return
	}

	w := make([]uint32, 68)
	nw := make([]uint32, 64)

	var i uint64
	for i = 0; i < inLen; i++ {
		ctx.buffer[ctx.pos] = input[i]
		ctx.msgLen++
		ctx.pos++
		//  while ctx.msgLen % 64 = 0
		if ctx.pos == 64 {
			msgExtend(ctx, w, nw)
			CF(ctx, w, nw)
			ctx.pos = 0
		}
	}
}

func Sm3Finish(ctx *Sm3Ctx) []uint8 {
	var i int

	length := (ctx.msgLen + 1) & 0x2f
	paddingLen := 1
	padding := []uint8{0x80}
	if length <= 56 {
		padding = append(padding, make([]uint8, 56-length)...)
		paddingLen += int(56 - length)
	} else {
		padding = append(padding, make([]uint8, 64-length+56)...)
		paddingLen += int(64 - length + 56)
	}
	padding = append(padding, down8bytes(ctx.msgLen<<3)...)
	paddingLen += 8

	w := make([]uint32, 68)
	nw := make([]uint32, 64)

	for i = 0; i < paddingLen; i++ {
		ctx.buffer[ctx.pos] = padding[i]
		ctx.pos++
		//  while ctx.msgLen % 64 = 0
		if ctx.pos == 64 {
			msgExtend(ctx, w, nw)
			CF(ctx, w, nw)
			ctx.pos = 0
		}
	}

	output := make([]uint8, Sm3HashSize)
	for i = 0; i < 8; i++ {
		output[4*i+0] = uint8((ctx.state[i] >> 24) & 0xff)
		output[4*i+1] = uint8((ctx.state[i] >> 16) & 0xff)
		output[4*i+2] = uint8((ctx.state[i] >> 8) & 0xff)
		output[4*i+3] = uint8(ctx.state[i] & 0xff)
	}
	return output
}

func Sm3Hash(input []uint8, inLen uint64) []uint8 {
	var ctx Sm3Ctx
	Sm3Init(&ctx)
	Sm3Update(&ctx, input, inLen)
	return Sm3Finish(&ctx)
}
